import ctypes
import inspect
import socket
import sys
import threading
import traceback
from queue import Queue
import pymysql

import facade
from .mthreading import ThreadManager
from DBUtils.PooledDB import PooledDB, SharedDBConnection


class ThreadPoolManger(object):
    """
    线程池管理器
    """

    def __init__(self, max_workers, logger=None, is_static_max=True, *args, **kwargs):
        if logger:
            self.logger = logger
        else:
            self.logger = facade.get_streamlogger()
        # 任务队列
        self.work_queue = Queue(100)
        # 结果队列 有时候结果需要在主线程处理或单独线程处理时使用
        self.result_queue = Queue(100)
        # 线程队列 主要管线程间的通信
        self.thread_queue = Queue(10)
        # 应该赋予一个方法 用于回调
        self.callback = None
        # 线程池的字典 有时候需要在线程池中维护一些数据，所以没有采用原来的只维护线程对象
        self.thread_pool_dicts = {}
        # 线程数量 反馈当前工作线程数量
        self.thread_num = 0
        # 工作线程定量 该变量是为了维持线程的数量一定
        self.max_workers = max_workers
        # 是否是固定最大值
        self.is_static_max = is_static_max
        # 线程池的参数 不在线程使用
        self.args = args
        self.kwargs = kwargs

        # 该标志将用于sql间的同步  现在发现一个问题 在 线程中执行select会出现
        # Command Out of Sync 错误  所以启用这个信号
        # 这个错误表示 对于同一个连接 在不同线程中当一个线程发出sql且没有返回结果期间，
        # 另一个线程页发出了请求，这时mysql的第一个请求在结果缓存中还没有返回 会冲突报错
        # 这里主要是设置任务和处理结果用到同一个连接导致
        # event.wait(timeout=None)：调用该方法的线程会被阻塞，如果设置了timeout参数，超时后，线程会停止阻塞继续执行；
        # event.set()：将event的标志设置为True，调用wait方法的所有线程将被唤醒；
        # event.clear()：将event的标志设置为False，调用wait方法的所有线程将被阻塞；
        # event.isSet()：判断event的标志是否为True。
        self.event = threading.Event()
        self.event_set()
        # 独立的线程变量，很多时候 多线程变量是共享且线程不安全的，
        # 该变量保证了线程内变量的安全
        # Python提供了 threading.local 类，将这个类实例化得到一个全局对象，
        # 但是不同的线程使用这个对象存储的数据其它线程不可见
        # (本质上就是不同的线程使用这个对象时为其创建一个独立的字典)。
        self.localVal = threading.local()

        # 初始化线程到线程池
        self.__init_threading_pool(self.max_workers)

    def set_mysqlpool(self, creator=pymysql,  # 使用链接数据库的模块
                      maxconnections=6,  # 连接池允许的最大连接数，0和None表示没有限制
                      mincached=2,  # 初始化时，连接池至少创建的空闲的连接，0表示不创建
                      maxcached=5,  # 连接池空闲的最多连接数，0和None表示没有限制
                      maxshared=3,
                      # 连接池中最多共享的连接数量，0和None表示全部共享，ps:其实并没有什么用，因为pymsql和MySQLDB等模块中的threadsafety都为1，所有值无论设置多少，_maxcahed永远为0，所以永远是所有链接共享
                      maxusage=None,  # 一个链接最多被使用的次数，None表示无限制
                      blocking=True,  # 链接池中如果没有可用共享连接后，是否阻塞等待，True表示等待，False表示不等待然后报错
                      setsession=[],  # 开始会话前执行的命令列表
                      ping=0,  # ping Mysql 服务端，检查服务是否可用
                      host='127.0.0.1',
                      port=3306,
                      user='',
                      password='',
                      database='',
                      charset='utf8'):
        self.mysql_pool = PooledDB(
            creator=creator,  # 使用链接数据库的模块
            maxconnections=maxconnections,  # 连接池允许的最大连接数，0和None表示没有限制
            mincached=mincached,  # 初始化时，连接池至少创建的空闲的连接，0表示不创建
            maxcached=maxcached,  # 连接池空闲的最多连接数，0和None表示没有限制
            maxshared=maxshared,
            maxusage=maxusage,  # 一个链接最多被使用的次数，None表示无限制
            # 连接池中最多共享的连接数量，0和None表示全部共享，ps:其实并没有什么用，因为pymsql和MySQLDB等模块中的threadsafety都为1，所有值无论设置多少，_maxcahed永远为0，所以永远是所有链接共享
            blocking=blocking,  # 链接池中如果没有可用共享连接后，是否阻塞等待，True表示等待，False表示不等待然后报错
            setsession=setsession,  # 开始会话前执行的命令列表
            ping=ping,  # ping Mysql 服务端，检查服务是否可用
            host=host,
            port=port,
            user=user,
            password=password,
            database=database,
            charset=charset
        )

    def set_args(self, args):
        self.args = args

    def set_kwargs(self, kwargs):
        self.kwargs = kwargs

    def set_work_queue(self, num):
        self.work_queue = Queue(num)

    def set_result_queue(self, num):
        self.result_queue = Queue(num)

    def set_is_static_max(self, is_static_max):
        self.is_static_max = is_static_max

    def set_max_workers(self, num):
        if self.is_static_max:
            self.logger.info("是静态最大线程数,不能对最大线程数进行设置")
        else:
            self.max_workers = num

    def get_max_workers(self):
        return self.max_workers

    def get_localVal(self):
        return self.localVal

    def set_localVal(self, val):
        self.localVal = val

    def set_callback(self, callback):
        self.callback = callback

    def event_set(self):
        """
        设置event 允许wait继续运行 状态为True
        :return:
        """
        self.event.set()

    def event_clear(self):
        """
        设置值为False 不允许运行其他线程
        :return:
        """
        self.event.clear()

    def event_is_set(self):
        """
        返回标志
        :return:
        """
        return self.event.is_set()

    def event_wait(self, timeout=None):
        self.event.wait(timeout)

    def __init_threading_pool(self, thread_num, *args, **kwargs):
        """
        初始化线程池，创建指定数量的线程池
        :param thread_num:
        :return:
        """
        self.logger.info("初始化工作线程数量为：%d" % thread_num)
        for i in range(int(thread_num)):
            # 新建一个线程
            thread = ThreadManager(self.work_queue, self.result_queue,
                                   self.thread_queue, self.logger,
                                   args=args, kwargs=kwargs)
            # 线程名
            threadName = "threading_work_{0}".format(i + 1)
            if threadName in self.thread_pool_dicts:
                self.logger.warn("线程名%s在线程池中已经存在，跳过" % threadName)
            else:
                # 设置线程名
                thread.setName(threadName)
                self.set_thread_pool_dicts(threadName, thread, threadtype="work", *args, **kwargs)
                self.thread_num = self.thread_num + 1

    def set_thread_pool_dicts(self, threadName, thread, threadtype="work", *args, **kwargs):
        """
        这里调用了一个钩子函数 可以在dicts中加入其他参数
        :param thread_pool_dicts: 线程池字典对象
        :param threadName:  线程名
        :param thread:  线程
        :param args:
        :param kwargs:
        :return:
        """
        dictsfull = {}
        dictsfull["thread"] = thread
        dictsfull["threadname"] = threadName
        dictsfull["kwargs"] = kwargs
        dictsfull["args"] = args
        dictsfull["threadtype"] = threadtype
        if self.callback:
            # 对象使用
            self.callback(self.thread_pool_dicts, dictsfull, args, kwargs)
        else:
            # 继承时使用
            self.thread_pool_hook(self.thread_pool_dicts, dictsfull, args, kwargs)
        self.thread_pool_dicts[threadName] = dictsfull

    def thread_pool_hook(self, thread_pool_dicts: dict, dictsfull: dict, *args, **kwargs):
        """
        钩子函数 可以被重写
        主要重写里面的dicts部分
        :return:
        """
        pass

    def get_workqueue_size(self):
        return self.work_queue.qsize()

    def get_resultqueue_size(self):
        return self.result_queue.qsize()

    def result_queue_is_empty(self):
        return self.result_queue.empty()

    def work_queue_is_empty(self):
        return self.work_queue.empty()

    def restart_thread(self, thread_name):
        """
        通过线程名重启线程
        :param thread_name:
        :return:
        """
        is_start = False
        if thread_name in self.thread_pool_dicts:
            if self.thread_pool_dicts[thread_name]["thread"].is_alive():
                is_start = True

            if not is_start:
                if not self.thread_pool_dicts[thread_name]["thread"].get_is_restart():
                    return False
                args = self.thread_pool_dicts[thread_name]["args"]
                kwargs = self.thread_pool_dicts[thread_name]["kwargs"]
                self.logger.info("新建线程{}".format(thread_name))
                # 新建一个线程
                thread = ThreadManager(self.work_queue, self.result_queue,
                                       self.thread_queue, self.logger,
                                       args=args, kwargs=kwargs)
                # 设置线程名
                thread.setName(thread_name)
                self.set_thread_pool_dicts(thread_name, thread, threadtype="work", *args, **kwargs)
                thread.start()
            return True
        else:
            self.logger.info("{}线程名不在记录中，无法重启".format(thread_name))
            return False

    def add_thread(self, thread_name, *args, **kwargs):
        """

        :param thread_name: 线程名
        :param args:
        :param kwargs:
        :return:
        """
        thread = ThreadManager(self.work_queue, self.result_queue,
                               self.thread_queue, self.logger,
                               args=args, kwargs=kwargs)
        if thread_name in self.thread_pool_dicts:
            self.logger.info("线程存在 无法添加")
            return False
        thread.setName(thread_name)
        self.logger.info("添加一个线程{}".format(thread_name))
        self.set_thread_pool_dicts(thread_name, thread, threadtype="work", *args, **kwargs)
        return True

    def add_more_thread(self, thread_num, *args, **kwargs):
        """
        添加多个线程，thread_num是添加线程的数量
        :param thread_num:
        :return:
        """
        # 判断可开线程的数量
        if self.thread_num < self.max_workers:
            if self.max_workers - self.thread_num < thread_num:
                thread_num = self.max_workers - self.thread_num
        else:
            self.logger.info("线程已达到最大量，不能再新开线程")
            return False
        # 添加新的线程
        count = 0
        num = 0
        while thread_num > count:
            num += 1
            thread_name = "threading_work__{}".format(num)
            if self.add_thread(thread_name, args, kwargs):
                count += 1
        self.get_thread_num()
        return True

    def delete_thread(self, thread_num):
        """
        删除多少个线程，thread_num为删除数量
        :param thread_num:
        :return:
        """
        try:
            i = 0
            for key in list(self.thread_pool_dicts.keys()):
                thread = self.thread_pool_dicts[key]["thread"]
                if not thread.runstatus:
                    # 优先删除处于等待任务的线程
                    thread.threadval.thread_delete = True
                    # self.stop_thread(thread)
                    del self.thread_pool_dicts[key]
                    i += 1
                elif thread.threadval.thread_delete:
                    # 删除该线程 表示线程池想删除(删除逻辑为我想删除 且程序内部允许删除时删除)
                    thread.threadval.thread_delete = True
                    del self.thread_pool_dicts[key]
                    i += 1
                if i >= thread_num:
                    break
        except:
            self.logger.info(traceback.format_exc())
        self.get_thread_num()

    def get_thread_num(self):
        work_thread = 0
        for name in list(self.thread_pool_dicts.keys()):
            if self.thread_pool_dicts[name]["threadtype"] == "work":
                work_thread += 1
        self.thread_num = work_thread
        return self.thread_num

    def set_thread_num(self, thread_num):
        """
        设置当前线程数量 改设置保证了线程数量 多的会被删除,少的会被添加
        :return:
        """
        if thread_num > self.get_thread_num():
            self.add_more_thread(thread_num - self.thread_num)
        else:
            self.delete_thread(self.get_thread_num() - thread_num)
        self.thread_num = thread_num

    def __get_super_thread(self, func, thread_name, *args, **kwargs):
        """
        获取一个原始的线程
        :param func:
        :param thread_name:
        :param args:
        :param kwargs:
        :return:
        """
        thread = ThreadManager(self.work_queue, self.result_queue, self.thread_queue,
                               self.logger, target=func, name=thread_name,
                               args=args, kwargs=kwargs)
        thread.set_mode("super")
        return thread

    def set_add_especial_thread(self, func, threadname, threadtype="especial", *args, **kwargs):
        """
        添加特殊的线程
        :return:
        """
        # 得到一个线程对象
        thread = self.__get_super_thread(func, threadname, *args, **kwargs)
        self.set_thread_pool_dicts(threadname, thread, threadtype=threadtype, *args, **kwargs)

    def add_job(self, func, *args, **kwargs):
        """
        将任务放入队列，等待线程池阻塞读取，参数是被执行的函数和函数的参数
        :param func:
        :param args:
        :param kwargs:
        :return:
        """
        self.work_queue.put((func, args, kwargs))

    def start(self, threadtype=None, threadname=None):
        # 开启单个线程
        if threadname:
            if self.thread_pool_dicts[threadname]:
                thread = self.thread_pool_dicts[threadname]["thread"]
                if thread and not thread.is_alive():
                    self.logger.info("启动线程{}".format(thread.getName()))
                    thread.start()
        elif threadtype:  # 开始某个类型的线程
            for threadname in self.thread_pool_dicts:
                if self.thread_pool_dicts[threadname] and self.thread_pool_dicts[threadname][
                    "threadtype"] == threadtype:
                    thread = self.thread_pool_dicts[threadname]["thread"]
                    if thread and not thread.is_alive():
                        self.logger.info("启动线程{}".format(thread.getName()))
                        thread.start()

    # 防止线程意外挂掉 挂掉线程重启
    def checkThread(self):
        # 工作线程还有数据重启线程才有意义
        if not self.work_queue_is_empty():
            # 用来保存当前线程名称
            nowThreadsName = []
            # 获取当前线程名
            now = threading.enumerate()
            for i in now:
                # 保存当前线程名称
                nowThreadsName.append(i.getName())
            # 循环线程池的key
            for name in list(self.thread_pool_dicts.keys()):
                # 取出线程对象
                thread = self.thread_pool_dicts[name]["thread"]
                if name in nowThreadsName and thread.is_alive():
                    pass  # 当前某线程名包含在初始化线程组中，可以认为线程仍在运行
                else:
                    self.restart_thread(name)  # 重启线程
            # 如果不是静态最大值 线程可以波动
            if not self.is_static_max:
                # 空闲线程
                free_thread = 0
                # 判断线程空闲情况
                for name in list(self.thread_pool_dicts.keys()):
                    thread = self.thread_pool_dicts[name]["thread"]
                    if not thread.runstatus and self.thread_pool_dicts[name]["threadtype"] == "work":
                        # 代表该线程等待工作队列，列入空闲线程
                        free_thread += 1

                # 允许冗余2个工作线程 避免在零界点不停的创建,删除
                if free_thread > 2:
                    self.delete_thread(free_thread - 2)

                if free_thread == 0 and not self.work_queue.empty():
                    self.add_more_thread(self.max_workers - self.get_thread_num())

    def checkThreadRunFinish(self):
        """
        通过等待的方式判断结束，等待是因为任务队列取出任务后在运行，还没放入结果队列
        :return:
        """
        if self.result_queue.empty() and self.work_queue.empty():
            for name in list(self.thread_pool_dicts.keys()):
                thread = self.thread_pool_dicts[name]["thread"]
                # 判断工作状态是否为True,任何一个在工作状态 都将判断为线程没运行完
                if thread.runstatus:
                    return False
            return True
        else:
            return False

    # 多网卡情况下，根据前缀获取IP
    def get_local_ip_by_prefix(self, prefix):
        localIP = ''
        for ip in socket.gethostbyname_ex(socket.gethostname())[2]:
            if ip.startswith(prefix):
                localIP = ip

        return localIP

    # 下面两个方法是强制退出线程的方法
    def _async_raise(self, tid, exctype):
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_thread(self, thread):
        if thread:
            try:
                self.logger.info("thread is {};thread.ident 的值为:{}".format(thread, thread.ident))
                if thread.ident:
                    self._async_raise(thread.ident, SystemExit)
            except ValueError as e:
                self.logger.info("已经运行完毕 不需要删除了")
        else:
            self.logger.info("传入的线程对象为空，理论上不应该出现这种逻辑 结束程序检查逻辑")
            sys.exit(-1)
